如何调用 AI 接口生成组件
现在写组件,不再需要从头写,完全可以告诉 AI 你的需求,让 AI 给你生成。
AI 生成的代码基本就是可用的,然后再改一改就可以了。
会用 copilot、cursor 等工具来生成代码。
这样是可以,但是定制性不强。
如果你要做这种 bolt.new、v0 这种根据你的需求生成网站全部代码的工具呢?
是不是就没思路了?
这时候就要学会自己调用 AI 接口了。
来试一下:
首先需要一个 API KEY。
国内调用国外的 ai 接口有重重阻碍。
所以一般都是找个国内的代理商的 api 来用。
这种代理挺多的,我这里用的 302.ai,你也可以用别的
这种代理都是给你对接好了国内、国外各种模型,可以直接用。
我这里充了 5 美元,也就是 40 人民币,可以用支付宝支付,这点确实方便:
国外的那些模型,你支付都是个问题。
生成一个 API KEY:
接下来就可以调用 openai 的接口了。
这些代理都只是做了一下转发,所以接口和 openai 的一模一样。
创建个项目:
mkdir openai-test
cd openai-test
npm init -y安装下 openai 的包:
npm install --save openai其实 AI 接口都是 rustful 的,但直接发请求比较麻烦,一般会基于 sdk 来调,也就是 npm 包。
创建 src/index.mjs
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: '你的 API KEY',
baseURL: 'https://api.302.ai/v1'
});
async function main() {
const stream = await client.chat.completions.create({
model: "gpt-4",
messages: [
{role: 'user', content: '生成一个 Table 的 React 组件'}
],
stream: true
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content || '');
}
}
main();.mjs 是告诉 node 这个文件是 es module 的。
这里要填入 baseURL 和 apiKey,这俩都用代理商的。
model 是指定用哪个模型。
messages 是上下文,也就是聊天记录。
stream 指定 true 就是流式返回内容。
跑一下:
生成的代码是用 table 写的:
如何再告诉它让它不要用 table,并且引入 sass 写下样式呢?
这样:
把回答放到一个 md 里。
然后在 messages 数组里加上这条聊天记录:
messages 数组就是上下文,如果基于之前聊的内容继续聊,那就把这些信息放到 message 数组里。
这里 role 为 asssitant 就是 AI 说的内容,role 为 user 就是用户说的内容。
const stream = await client.chat.completions.create({
model: "gpt-4",
messages: [
{role: 'user', content: '生成一个 Table 的 React 组件'},
{role: 'assistant', content: fs.readFileSync('./src/response1.md', 'utf-8')},
{role: 'user', content: '在这个基础上加上 sass 写下样式,并且不要用 table'}
],
stream: true
});再跑下:
可以看到,它对之前的 Table 组件做了下修改,没用 table 标签了,并且加上了 sass 来写样式。
但现在生成的代码比较随意,实际上我们的项目都是有代码规范的。
如何指定生成代码的规范,回答的格式呢?
在 system 里指定:
创建 system.md
# Role: 前端工程师
## Profile
- author: 神光
- language: 中文
- description: 你非常擅长写 React 组件
## Goals
- 根据用户需求生成组件代码
## Skills
- 熟练掌握 typescript
- 会写高质量的 React 组件
## Constraints
- 用到的组件来源于 antd
- 样式用 scss 写
## Workflows
根据用户描述生成的组件,规范如下:
组件包含 4 类文件:
1、index.ts
这个文件中的内容如下:
export { default as [组件名] } from './[组件名]';
export type { [组件名]Props } from './interface';
2、interface.ts
这个文件中的内容如下,请把组件的props内容补充完整:
interface [组件名]Props {}
export type { [组件名]Props };
4、[组件名].tsx
这个文件中存放组件的真正业务逻辑,不能编写内联样式,如果需要样式必须在 5、styles.ts 中编写样式再导出给本文件用
5、styles.scss
这个文件中必须用 scss 给组件写样式,导出提供给 4、[组件名].tsx
## Initialization
作为前端工程师,你知道你的[Goals],掌握技能[Skills],记住[Constraints], 与用户对话,并按照[Workflows]进行回答,提供组件生成服务就是通过 markdown 来指定这个机器人都会啥,也就是角色。
你用的一些 AI 聊天工具基本都内置了很多 system 设置:
比如 AI 菩萨、人生导师啥的。
在 messages 里指定这个 system 设置:
这样,AI 的回答方式就被限定了。
至此,system、user、assistant 这三个 role 就都知道是啥了。
跑一下:
现在的回答是不是顺眼多了?
要求的 index.ts、interface.ts、Table.tsx、styles.scss 就都给生成了。
那如何把这些代码提取出来跑呢?
手动复制当然是可以的,但是要做自动化工具,自然就要把这一步自动化了。
主流的方案其实很简单,就是正则匹配。
他就是可以提取回答中的代码并运行:
它的实现原理就是正则匹配代码段:
所以要做 bolt.new 这种工具也可以用正则匹配把代码摘出来:
当然,其实还有种更方便的方式,就是 tools,之前叫 function calls。
用了它之后就不返回消息了,而是返回一个函数调用,具体的参数格式可以指定。
这样用:
传入 tools 数组,创建一个 getCode 的函数,指定参数是一个对象,有 code1、code2、code3、code4 这四个属性,内容分别是 4 个文件的内容。
这次不用 stream 返回了:
import OpenAI from 'openai';
import fs from 'node:fs';
const client = new OpenAI({
apiKey: '你的 API KEY',
baseURL: 'https://api.302.ai/v1'
});
async function main() {
const stream = await client.chat.completions.create({
model: "gpt-4",
messages: [
{role: 'system', content: fs.readFileSync('./src/system.md', 'utf-8')},
{role: 'user', content: '生成一个 Table 的 React 组件'},
{role: 'assistant', content: fs.readFileSync('./src/response1.md', 'utf-8')},
{role: 'user', content: '在这个基础上加上 sass 写下样式,并且不要用 table'}
],
tools: [
{
type: "function",
function: {
name: "getCode",
description: "生成的组件代码",
parameters: {
type: "object",
properties: {
code1: {
type: "string",
description: "生成的 index.ts 代码"
},
code2: {
type: "string",
description: "生成的 interface.ts 代码"
},
code3: {
type: "string",
description: "生成的 [组件名].tsx 代码"
},
code4: {
type: "string",
description: "生成的 styles.ts 代码"
},
},
required: ["code1", 'code2', 'code3', 'code4']
}
}
},
],
// stream: true
});
console.log(stream.choices[0].message.tool_calls[0].function)
}
main();跑一下:
可以看到,返回的不就是想要的格式么?
还用自己正则解析么?
直接 parse 下这个 JSON 就行。
生成的代码我发现用到了 UserList,这个是不需要的,改下 prompt:
{role: 'user', content: '在这个基础上加上 sass 写下样式,并且不要用 table,有 name、age、email 三列,数据是参数传入的'}再跑下:
复制出来,parse 下,然后写入文件:
创建 test.mjs
import fs from 'node:fs';
const res = {
name: 'getCode',
arguments: '{
' +
`"code1": "export { default as UserTable } from './UserTable';\
export type { UserTableProps } from './interface';\
",
` +
'"code2": "interface UserTableProps {\
users: {\
name: string;\
age: number;\
email: string;\
}[];\
}\
export type { UserTableProps };\
",
' +
`"code3": "import React from 'react';\
import { UserTableProps } from './interface';\
import './styles.scss';\
\
const UserTable: React.FC<UserTableProps> = ({users}) => {\
return (\
<div className='user-table'>\
<div className='user-table-row user-table-header'>\
<div className='user-table-cell'>Name</div>\
<div className='user-table-cell'>Age</div>\
<div className='user-table-cell'>Email</div>\
</div>\
{users.map((user, index) => (\
<div key={index} className='user-table-row'>\
<div className='user-table-cell'>{user.name}</div>\
<div className='user-table-cell'>{user.age}</div>\
<div className='user-table-cell'>{user.email}</div>\
</div>\
))}\
</div>\
)\
};\
\
export default UserTable;\
",
` +
'"code4": ".user-table {\
display: flex;\
flex-direction: column;\
margin: 20px;\
}\
\
.user-table-row {\
display: flex;\
}\
\
.user-table-row.user-table-header {\
font-weight: bold;\
}\
\
.user-table-cell {\
flex: 1;\
padding: 10px;\
border: 1px solid #ddd;\
}\
"
' +
'}'
}
const codes = JSON.parse(res.arguments);
fs.mkdirSync('./Table');
fs.writeFileSync('./Table/index.ts', codes.code1);
fs.writeFileSync('./Table/interface.ts', codes.code2);
fs.writeFileSync('./Table/UserTable.tsx', codes.code3);
fs.writeFileSync('./Table/styles.scss', codes.code4);跑一下:
node ./src/test.mjs可以看到,写入的文件都是对的。
然后创建个 vite 项目,把它放进去试试:
npx create-vite react-test-project安装下依赖:
npm install
npm install --save-dev sass把 Table 目录复制到 src,在 App.tsx 引入下:
import { UserTable } from './Table'
function App() {
return <UserTable users={[
{
name: 'guang',
age: 20,
email: 'guang@guang.com'
},
{
name: 'dong',
age: 18,
email: 'dong@dong.com'
}
]}/>
}
export default App还要去掉 main.tsx 里的 index.css
跑一下:
npm run dev可以看到,Table 组件正常渲染出来了:
当然,这个组件比较简单,你可以让 AI 生成更复杂的组件,或者生成之后让它继续完善。
但流程是一样的。
这样,是不是可以写个 CLI 工具,输入组件名、组件描述,让 AI 生成符合规范的代码,然后写入文件呢?
或者像 bolt.new 这样,生成整个项目的代码,然后跑起来呢?
其实都是可以的。
代码上传了文档仓库
总结
本文学了下如何调用 openai 的接口。
通过 openai 的 npm 包来调,找一个国内代理商的 API_KEY 就可以了。
messages 就是上下文,可以指定三种 role:user 是用户,assistant 是 AI,system 是预先对 AI做一些设置,比如让它输出的代码都符合某种规范,用什么格式回答等。
这样回答至少是按照规定的格式来的。
不符合需求的话可以让 AI 再改,把聊天过程添加到 messages 数组(也就是上下文)就可以了。
提取回答中的代码,主流的方案是正则匹配,但今天用了 tools 的方式来提取的代码,它是让 AI 的回答是按照函数调用的方式,生成某种格式的参数。再解析参数 json 就可以了。
之后把生成的代码写入磁盘,跑了下试试,能够正常跑。
各种 AI 生成代码的工具其实都可以自己做,或者你可以做个 CLI 工具、或者做个 VSCode 插件、或者做个网站,都是可以的。
只会用 AI 工具是不够的,你还得会调 AI 接口,这样才能定制,做一些特定场景的提效工具。